package com.raspositos.guitarfrik.android;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;

import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.net.Uri;
import android.os.CountDownTimer;
import android.os.Vibrator;
import android.util.Log;

public class IntroMediaPlayer implements OnErrorListener, OnCompletionListener {

	public interface ErrorMessenger {

		 void post(int resource, Object ... args);

	}

	private String song;
	private Prefs prefs;
	private MediaPlayer player;
	private State state;
	private final class SectionCountDownTimer extends CountDownTimer {
		private SectionCountDownTimer(long millisInFuture,
				long countDownInterval) {
			super(millisInFuture, countDownInterval);
//			log("SectionCountDownTimer:"+millisInFuture + "@"+this);
		}

		public void onFinish() {
			timer = null;
//			log("onFinish@"+this);
			onCompletion(player);
		}

		public void onTick(long tick) {
		}
	}
	
	private static final void log(final String method){
//		Main.out.println("IMP: method:" + method + ", State:" + state + ", song:" + song + ", section:" + section  );
	}
	
	public enum State { IDLE, INTRO, PREPARED, PLAYING, PAUSED, DEAD}
	public int introCount;
	private TrackSection section;
	private CountDownTimer timer;
	private Context context;
	private Vibrator vibr;
	private int pausedAt;
	private OnPlay onPlay;
	private ErrorMessenger errMsg;
	
	public IntroMediaPlayer(Context ctxt, ErrorMessenger msg){
		context = ctxt;
		player = new MediaPlayer();
		player.setOnErrorListener(this);
		player.setOnCompletionListener(this);
		state = State.IDLE;
		introCount = 0;
		pausedAt = 0;
		prefs = Prefs.getPrefs(context);
		vibr = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
		errMsg = msg;
		//log("Constructor");
	}
	public boolean onError(MediaPlayer plyr, int what, int extra) {
		reset();
//		log("Error");
		switch (what) {
		case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
			errMsg.post(R.string.err_server_died);
			break;

		default:
			errMsg.post(R.string.err_unkown, "MediaPlayer code:" + what);

			break;
		}
		
		return true;
	}
	private void reset() {
		player.release();
		player = new MediaPlayer();
		player.setOnErrorListener(this);
		player.setOnCompletionListener(this);
		state = State.IDLE;
		introCount =0;
	}
	public void onCompletion(MediaPlayer plyr) {
		log("OnCompletion");
//		trace("OnCompletion");
		if (state != State.DEAD){
			//player.stop();
			//MOST AWFUL WORKAROUND EVER
			State prevState = state;
			int count = introCount;
			reset();
			introCount = count;
			state = prevState;
			switch (state){
			case INTRO:
				setIntroDataSource();
				break;
//			case PREPARED:
//				try {
//					player.setDataSource(getSongDataSource());
//				} catch (IllegalArgumentException e) {
//					log(e.toString());
////					e.printStackTrace(Main.out);
//					Log.e("GuitarFrik", toString(), e);
//					errMsg.post(R.string.err_unkown);
//					reset();
//				} catch (IllegalStateException e) {
//					log(e.toString());
////					e.printStackTrace(Main.out);
//					Log.e("GuitarFrik", toString(), e);
//					errMsg.post(R.string.err_unkown);
//					reset();
//				} catch (IOException e) {
//					log(e.toString());
////					e.printStackTrace(Main.out);
//					Log.e("GuitarFrik", toString(), e);
//					errMsg.post(R.string.err_io, song);
//					reset();
//				}
//				break;
			}
			//END WORKAROUND
		}
		switch (state){
		case INTRO: //Play intro until enough hits, then play song
			if (introCount < prefs.getIntroHits()){
				play();
			} else {
				state = State.PREPARED;
				play();
			}
			break;
		case PLAYING: //Repeat and play intro
			//PART OF THE WORKAROUND player.reset();
				state = State.IDLE;
				play();
		}
	}
	
//	private void trace(String string) {
//	Log.d("GuitarFrik", string);
//	Log.d("GuitarFrik", Arrays.asList(Thread.currentThread().getStackTrace()).toString());
//	}
	/**
	 * Caller must make sure that play is not directly called during INTRO or  PREPARED
	 */
	public void play(){
		log("Play");
		try {
			switch (state) {
			case IDLE://From Nothing
				if (getSongDataSource() == null ){
					return;
				}
				state = State.INTRO;
				setIntroDataSource();
				player.prepare();
				introCount = 1;
				vibrate();
				player.start();
				break;
			case INTRO://Intro Playing;
				player.prepare();
				introCount++;
				vibrate();
				player.start();
				break;
			case PREPARED://Intro Played
				//PART OF THE WORKAROUND player.reset();
				State prevState = state;
				int count = introCount;
				reset();
				introCount = count;
				state = prevState;
				//END WORKAROUND
				player.setDataSource(getSongDataSource());
				player.prepare();
				state = State.PLAYING;
				seekToStart();
				player.start();
				setupTimer();
				if (onPlay != null){
					onPlay.play();
				}
				break;
			case PLAYING://Stop and from the top
				player.stop();
				player.reset();
				pauseTimer();
				if (onPlay != null ){
					onPlay.stopped();
				}
				state = State.IDLE;
				play();
				break;
			case PAUSED://Play Intro and Continue
				state = State.INTRO;
				player.stop();
				player.reset();
				setIntroDataSource();
				player.prepare();
				introCount = 1;
				player.start();
				break;
			default:
				break;
			}
		} catch (IllegalArgumentException e) {
			log(e.toString());
//			e.printStackTrace(Main.out);
			Log.e("GuitarFrik", toString(), e);
			errMsg.post(R.string.err_unkown, e.toString());
			reset();
		} catch (IllegalStateException e) {
			log(e.toString());
//			e.printStackTrace(Main.out);
			Log.e("GuitarFrik", toString(), e);
			errMsg.post(R.string.err_unkown, e.toString());
			reset();
		} catch (IOException e) {
			log(e.toString());
//			e.printStackTrace(Main.out);
			Log.e("GuitarFrik", toString(), e);
			errMsg.post(R.string.err_io, song);
			reset();
		}
	}
	//Song Starts in time T0
	//Timer Started in time T1 set to stop at T2. Which is T1 + T2 = T4 in absolute terms of song duration from 0
	//Pause Came in time T3 = current player position. Time remaining T5 = T4 - T3
	//I.e. offset = 10, duration = 7. Scheduled Stop = 17
	// Paused at offset + 5. Current Position = 15
	// T5 =  (10 + 7) - 15 = 2 seconds left to play
	private void continueTimer() {
		if (section != null ){
			long timeToPlay = section.getOffset() + section.getDuration() - player.getCurrentPosition();
			timer = new SectionCountDownTimer(timeToPlay, timeToPlay);
			timer.start();
		}
	}
	private void setupTimer() {
		if (section != null ){
			timer = new SectionCountDownTimer(section.getDuration(), section.getDuration());
			timer.start();
		}
	}
	public State getState(){
		return state;
	}
	
	public void setSongSource(String source){
		song = source;
	}
	
	public void setTrackSection(TrackSection section){
		this.section = section;
		if (section == null && state == State.PLAYING){
			pauseTimer();
		}
	}
	
	private void seekToStart() {
		if (section != null ){
			player.seekTo(section.getOffset());
		} else if (pausedAt != 0){
			player.seekTo(pausedAt);
			pausedAt = 0;
			continueTimer();
		}
	}
	public void pause(){
//		log("Pause");
		switch(state){
		case INTRO:
		case PLAYING:
			state = State.PAUSED;
			player.pause();
			pausedAt = player.getCurrentPosition();
			pauseTimer();
			if (onPlay != null ){
				onPlay.stopped();
			}
			break;
		case PAUSED: //Assume un-pause
			play();
			break;
		case IDLE:
		case PREPARED:
			break;
		}
	}
	
	private void pauseTimer() {
		if (timer != null){
			timer.cancel();
		}
		timer = null;
	}
	public void stop(){
//		log("Stop");
		if (state != State.IDLE){
			player.stop();
		}
		if (timer != null){
			timer.cancel();
		}
		timer = null;
		player.reset();
		state = State.IDLE;
		pausedAt = 0;
		introCount = 0;
		if (onPlay != null ){
			onPlay.stopped();
		}
	}
	
	private void vibrate() {
		if (prefs.getVibrate()) {
			if (introCount + 1 >= prefs.getIntroHits()) {
				vibr.vibrate(100);
			} else {
				vibr.vibrate(50);
			}
		}
	}

	
	public String getSongDataSource() {
		return song;
	}
	
	private void setIntroDataSource() {
		Prefs.IntroTypes iType = prefs.getIntroType();
		FileDescriptor dataSource = null;
		switch (iType) {
		case BUILT_IN:
			try {
//				AssetFileDescriptor rawFD = context.getResources().openRawResourceFd(Integer.parseInt(prefs.getIntroRef()));
//				dataSource = rawFD.getFileDescriptor();
//				player.setDataSource(dataSource, rawFD.getStartOffset(), rawFD.getLength());
//				rawFD.close();
				Uri uri = Uri.fromFile(context.getFileStreamPath(prefs.getIntroRef()));
//				Log.d("GuitarFrik", "Intro Uri:"+uri);
				player.setDataSource(context, uri);
			} catch (IllegalArgumentException e) {
				log(e.toString());
//				e.printStackTrace(Main.out);
				Log.e("GuitarFrik", toString(), e);
				errMsg.post(R.string.err_unkown, e.toString());
				reset();
			} catch (IllegalStateException e) {
				log(e.toString());
//				e.printStackTrace(Main.out);
				Log.e("GuitarFrik", toString(), e);
				errMsg.post(R.string.err_unkown, e.toString());
				reset();
			} catch (IOException e) {
				log(e.toString());
//				e.printStackTrace(Main.out);
				Log.e("GuitarFrik", toString(), e);
				errMsg.post(R.string.err_ringtone, prefs.getIntroRef());
				reset();
			}
			break;
		case RINGTONE:
			try {
				FileInputStream fis = new FileInputStream(prefs.getIntroRef());
				dataSource = fis.getFD();
				player.setDataSource(dataSource);
				fis.close();
			} catch (IOException e) {
				log(e.toString());
//				e.printStackTrace(Main.out);
				Log.e("GuitarFrik", toString(), e);
				errMsg.post(R.string.err_ringtone, prefs.getIntroRef());
				reset();
			}
		default:
			break;
		}
//		log("IntroDataSource:" + prefs.getIntroRef());
	}
	
	
	public int getCurrentPosition() {
		//log("getCurrPosition");
		return player.getCurrentPosition();
	}

	public int getDuration() {
		//log("getDuration");
		return player.getDuration();
	}
	
	public void seekTo(int time){
		switch (state) {
		case PLAYING:
		case PAUSED:
			pauseTimer();
			timer = null;
			section = null;
			player.seekTo(time);
			break;
		default:
			break;
		}
	   }
	public boolean isPlaying() {
		return state == State.PLAYING || state == State.INTRO || state == State.PREPARED;
	}
	/**
	 * @return the onPlay
	 */
	public final OnPlay getOnPlay() {
		return onPlay;
	}
	/**
	 * @param onPlay the onPlay to set
	 */
	public final void setOnPlay(OnPlay onPlay) {
		this.onPlay = onPlay;
	}
	
	public void release() {
//		log("release");
		state = State.DEAD;
		player.release();
	}
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("IntroMediaPlayer [state=").append(state).append(
				", song=").append(song).append(", section=").append(section)
				.append("]");
		return builder.toString();
	}
	
}
